// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: jhkim

#pragma once

#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include "coin2/exchange/base/symbology/product.h"

namespace coin2::strategy::util {

using ::coin2::exchange::base::symbology::IProduct;

namespace detail {
inline std::string GetProductKey(const IProduct& product) {
  return fmt::format("{}.{}", product.absolute_norm(), product.exchange_name());
}
}

enum AlarmState {
  ALARM_OFF,
  ALARM_ON,
};

class AlarmChecker {
 public:
  AlarmChecker(
      const AlarmCondition& almcond,
      int64_t timestamp)
      : almcond_(almcond) {
    if (almcond.has_product1()) {
      CHECK_GT(almcond.product1().products().norms_size(), 0);
      product1_ = CreateProductFromNormString(
          MarketExchangeApi::FromString(almcond.product1().mea()),
          almcond.product1().products().norms()[0],
          timestamp);
      key1_ = detail::GetProductKey(*product1_);
    }
    if (almcond.has_product2()) {
      CHECK_GT(almcond.product2().products().norms_size(), 0);
      product2_ = CreateProductFromNormString(
          MarketExchangeApi::FromString(almcond.product2().mea()),
          almcond.product2().products().norms()[0],
          timestamp);
      key2_ = detail::GetProductKey(*product2_);
    }
  }

  void TriggerAlarm() {
    state = ALARM_ON;
    LOG_EVERY_N(ERROR, 50) << "ALARM ON: " << almcond_.ShortDebugString();
  }

  void onBookFeed(const exchange::base::feed::FeedUpdate& update) {
    const auto& book = update.book();
    if (!book.Ask0() || !book.Bid0()) return;
    double midp = (book.Ask0()->price + book.Bid0()->price) * 0.5;
    std::string update_pkey = detail::GetProductKey(update.product());
    if (key1_ && *key1_ == update_pkey) {
      midp1_ = midp;
    } else if (key2_ && *key2_ == update_pkey) {
      midp2_ = midp;
    } else {
      return;
    }
    state = ALARM_OFF;
    if (midp1_ && midp2_) {
      double div12 = *midp1_ / *midp2_;
      LOG_EVERY_N(INFO, 500) << "div12: " << div12
          << " " << almcond_.ShortDebugString();
      if (almcond_.has_div12_lb() && div12 < almcond_.div12_lb()) {
        TriggerAlarm();
        return;
      }
      if (almcond_.has_div12_ub() && div12 > almcond_.div12_ub()) {
        TriggerAlarm();
        return;
      }
    }
  }

  std::optional<std::string>& GetKey1() { return key1_; }
  std::optional<std::string>& GetKey2() { return key2_; }

  AlarmState state;

 private:
  AlarmCondition almcond_;
  std::unique_ptr<IProduct> product1_;
  std::unique_ptr<IProduct> product2_;
  std::optional<double> midp1_;
  std::optional<double> midp2_;
  std::optional<std::string> key1_;
  std::optional<std::string> key2_;
};

class AlarmManager {
 public:
  AlarmManager(
      const AlarmConfig& almconf,
      int64_t timestamp) : almconf_(almconf) {
    for (int i = 0; i < almconf_.alarms_size(); ++i) {
      alarms_.push_back(std::make_unique<AlarmChecker>(
          almconf_.alarms(i),
          timestamp));
    }
  }

  void onBookFeed(const exchange::base::feed::FeedUpdate& update) {
    state = ALARM_OFF;
    for (auto&& alarm : alarms_) {
      alarm->onBookFeed(update);
      if (alarm->state == ALARM_ON) {
        state = ALARM_ON;
      }
    }
  }

  AlarmState state;

 private:
  std::vector<std::unique_ptr<AlarmChecker>> alarms_;
  AlarmConfig almconf_;
};

class AlarmHelper {
 public:
  AlarmHelper(
      const AlarmConfig& almconf,
      int64_t timestamp) : almconf_(almconf) {
    for (int i = 0; i < almconf_.alarms_size(); ++i) {
      alarms_.push_back(std::make_unique<AlarmChecker>(
          almconf_.alarms(i),
          timestamp));
      AlarmChecker* p = alarms_.back().get();
      const auto& key1 = p->GetKey1();
      const auto& key2 = p->GetKey2();
      if (key1) {
        alarm_map_[key1.value()] = p;
      }
      if (key2) {
        alarm_map_[key2.value()] = p;
      }
    }
  }

  AlarmChecker* GetAlarm(const IProduct& product) {
    std::string key = detail::GetProductKey(product);
    if (alarm_map_.find(key) != alarm_map_.end()) {
      return alarm_map_[key];
    }
    return nullptr;
  }

 private:
  std::vector<std::unique_ptr<AlarmChecker>> alarms_;
  std::unordered_map<std::string, AlarmChecker*> alarm_map_;
  AlarmConfig almconf_;
};

}  // namespace coin2::strategy::util
